﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using OverStore.Runtime.PersistenceServiceProvider;

namespace OverStore.Runtime.ObjectRepository.CallbackManager
{
    /// <summary>
    /// <para>
    ///     Maintains object that is references by instance of <typeparamref name="TInstance"/> class (via property or method etc).
    ///     Such object should be saved, loaded and deleted when <typeparamref name="TInstance"/> is persisted.
    ///     Aggregates are usually stored in database as one-to-many assosiation, where <typeparamref name="TInstance"/> is
    ///     'one' side, and <typeparamref name="TAggregate"/> is 'many' side of assosiation.
    /// </para>
    /// <para>
    ///     Aggregates are usually collection, but sometimes aggregate may be single instance, for example,
    ///     one-to-one relation.
    /// </para>
    /// <para>
    ///     Aggregates are stored in different tables in database, and represented via set of 
    ///     objects, collection of different types, but logically it is single object,
    ///     and often separated parts of object hasn't sense without any of such part.
    /// </para>
    /// <para>
    ///     If you need attaching support of your aggregates that are not mutable collections,
    ///     you need provide write access to aggreagtes, because attaching in general can't 
    ///     attach the same instance as proposed to attaching.
    /// </para>
    /// <para>
    /// Consider actions must be performed on <c>instance.Values</c> (where Values is collection of TAggregate items)
    /// when <c>instance</c> is persisted:
    /// <list type="bullet">
    ///     <item>
    ///         <term>Loading (and reloading):</term>
    ///         <description>
    ///             Aggregate must be reloaded with instance.
    ///         </description>
    ///     </item>
    ///     <item>
    ///         <term> Adding (registering instance in session, not physical inserting):</term>
    ///         <description>
    ///             Aggregate must be added with instance.
    ///         </description>
    ///     </item>
    ///     <item>
    ///         <term>Marking as deleted:</term>
    ///         <description>
    ///             Aggregate must be marked as deleted.
    ///         </description>
    ///     </item>
    ///     <item>
    ///         <term>Attaching:</term>
    ///         <description>
    ///             Aggregate must be attached too. It requires from aggregate to have a read-write access.
    ///         </description>
    ///     </item>
    ///     <item>
    ///         <term>Accepting changes:</term>
    ///         <description>Aggreate's changes must be accepted.</description>
    ///     </item>
    ///     <item>
    ///         <term>Saving:</term>
    ///         <list type="bullet">
    ///             <item>
    ///                 <term>Instance is not modified:</term>
    ///                 <description>Aggregate must be saved</description>
    ///             </item>
    ///             <item>
    ///                 <term>Instance is new:</term>
    ///                 <description>
    ///                     Aggregate must be saved <b>after</b> instance inserting.
    ///                     In most cases aggregates relate to instance by instance's 
    ///                     foreign key, so for new instance foreign key will be known 
    ///                     after inserting.
    ///                 </description>
    ///             </item>
    ///             <item>
    ///                 <term>Instance is modified:</term>
    ///                 <description>
    ///                     Aggregates must be saved before or after instance saving.
    ///                     In OverStore aggregates are saved after instance saving.
    ///                 </description>
    ///             </item>
    ///             <item>
    ///                 <term>Instance is deleted:</term>
    ///                 <description>
    ///                     Aggregates must be deleted <b>before</b> instance deletion.
    ///                     Otherwise foreign key violation may occurs, if instance-aggregate
    ///                     foreign key does not support cascade deletion.
    ///                 </description>
    ///             </item>
    ///         </list>
    ///     </item>
    /// </list>
    /// </para>
    /// </summary>
    /// <typeparam name="TInstance">The type of the instance.</typeparam>
    /// <typeparam name="TAggregate">The type of the aggregate.</typeparam>
    public class Aggregate<TInstance, TAggregate> : ICallbackManager<TInstance>
        where TInstance : class
        where TAggregate : class
    {
        private readonly IPersistenceServiceProvider _Provider;

        private readonly Func<TInstance, IEnumerable<TAggregate>> _GetCollectionAggregate;
        private readonly Action<TInstance, IEnumerable<TAggregate>> _SetCollectionAggregate;
        private readonly Action<TInstance, TAggregate, IPersistenceServiceProvider> _RestoreRelation;
        private bool _UseInstanceAsParameterForAggregateLoading;

        /// <summary>
        /// Initializes a new instance of the <see cref="Aggregate&lt;TInstance, TAggregate&gt;"/> class.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <param name="getCollectionAggregate">The get collection aggregate.</param>
        /// <param name="setCollectionAggregate">The set collection aggregate.</param>
        /// <param name="restoreRelation">Function that restores reference to parent in aggregate instance.</param>
        /// <param name="useInstanceAsParameterForAggregateLoading">if set to <c>true</c> [use instance as parameter for aggregate loading].</param>
        public Aggregate(
            IPersistenceServiceProvider provider, 
            Func<TInstance, IEnumerable<TAggregate>> getCollectionAggregate,
            Action<TInstance, IEnumerable<TAggregate>> setCollectionAggregate,
            Action<TInstance, TAggregate, IPersistenceServiceProvider> restoreRelation,
            bool useInstanceAsParameterForAggregateLoading)
        {
            if (Object.ReferenceEquals(getCollectionAggregate, null))
                throw new ArgumentNullException("getCollectionAggregate");
            if (Object.ReferenceEquals(provider, null))
                throw new ArgumentNullException("provider");
            if (Object.ReferenceEquals(restoreRelation, null))
                throw new ArgumentNullException("restoreRelation");


            _Provider = provider;

            _GetCollectionAggregate = getCollectionAggregate;
            _SetCollectionAggregate = setCollectionAggregate;
            _RestoreRelation = restoreRelation;

            _UseInstanceAsParameterForAggregateLoading = useInstanceAsParameterForAggregateLoading;
        }

        /// <summary>
        /// Performs operations as response to specified action.
        /// If method returns <c>false</c>, the operation which provides callback must be interrupted.
        /// Some operation can't be interrupted.
        /// </summary>
        /// <param name="instance">The instance been argument of persistence operation.</param>
        /// <param name="operation">The persistence operation performed on instance.</param>
        /// <returns>
        /// 	<c>true</c> to continue persistence operation, <c>false</c> to stop persistence operation.
        /// </returns>
        /// <exception cref="PersistingRuntimeException">Error occured due callback processing.</exception>
        public bool Callback(TInstance instance, PersistenceOperation operation)
        {
            var collection = _GetCollectionAggregate(instance);
            if (ReferenceEquals(collection, null))
                return true;
            switch (operation)
            {
                case PersistenceOperation.Loaded:
                    {
                        if (_UseInstanceAsParameterForAggregateLoading)
                            _Provider.ReloadCollection(collection, instance);
                        else
                            _Provider.ReloadCollection(collection);
                    }
                    break;
                case PersistenceOperation.Adding:
                    _Provider.AddCollection(collection);
                    break;
                case PersistenceOperation.MarkingAsDeleted:
                    _Provider.MarkCollectionAsDeleted(collection);
                    break;
                case PersistenceOperation.Attaching:
                    {
                        var attached = _Provider.AttachCollection(collection);
                        if (!ReferenceEquals(attached, collection) && _SetCollectionAggregate != null)
                            _SetCollectionAggregate(instance, attached);
                    }
                    break;
                case PersistenceOperation.NotModified:
                case PersistenceOperation.Updated:
                case PersistenceOperation.Inserted:
                case PersistenceOperation.Deleting:
                    foreach (var item in collection)
                        _RestoreRelation(instance, item, _Provider);
                    _Provider.SaveCollection(collection);
                    break;
            }
            return true;
        }
    }
}
